﻿using System;
using System.IO;
using ParserGenerator.Collections;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.Input;
using ParserGenerator.Output;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.CodeGeneration
{
    [Serializable]
    public class CSharpSyntacticActionWriter : CSharpExpressionWriter
    {
        public Identifier ReduceMethodName { get; set; }
        public string ReduceMethodAccessModifier { get; set; }

        public bool UseStaticReduceMethod { get; set; }
        public bool UseMoreParameters { get; set; }
        public bool UseArgumentCheck { get; set; }

        public Identifier ProductionNumberParameterName { get; }

        public string ArrayListNamespace { get; }
        public Identifier ArrayListClassName { get; }

        public string SourceContextNamespace { get; }
        public Identifier SourceContextClassName { get; }

        public Identifier ProductionNumberMustBeNonNegativeResourceName { get; }
        public string ProductionNumberMustBeNonNegativeResourceText { get; }

        public Identifier ProductionNumberOutOfRangeResourceName { get; }
        public string ProductionNumberOutOfRangeResourceText { get; }

        internal CSharpSyntacticActionWriter(GrammarDeclaration grammar)
            : base(grammar)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));

            ReduceMethodName = Identifier.New(grammar.GetOptionValue("pg.parser.reduce.name", "Reduce"));
            ReduceMethodAccessModifier = grammar.GetOptionValue("pg.parser.reduce.access", "private");

            UseStaticReduceMethod = grammar.GetOptionValue("pg.parser.reduce.static", false);
            UseMoreParameters = grammar.GetOptionValue("pg.parser.reduce.moreparams", false);
            UseArgumentCheck = grammar.GetOptionValue("pg.parser.reduce.checkargs", false);

            ProductionNumberParameterName = Identifier.New(grammar.GetOptionValue("pg.parser.reduce.prod.name", "prod"));

            ArrayListNamespace = grammar.GetOptionValue("pg.arraylist.namespace", "System.Collections.Generic");
            ArrayListClassName = Identifier.New(grammar.GetOptionValue("pg.arraylist.classname", "List"));

            SourceContextNamespace = grammar.GetOptionValue("pg.sourcecontext.namespace", "ParserGenerator.LexicalAnalysis");
            SourceContextClassName = Identifier.New(grammar.GetOptionValue("pg.sourcecontext.classname", "SourceContext"));

            ProductionNumberMustBeNonNegativeResourceName = Identifier.New(grammar.GetOptionValue("pg.reduce.sr.ProductionNumberMustBeNonNegative.name", "ProductionNumberMustBeNonNegative"));
            ProductionNumberMustBeNonNegativeResourceText = grammar.GetOptionValue("pg.reduce.sr.ProductionNumberMustBeNonNegative.text", "Production number must be non-negative.");

            ProductionNumberOutOfRangeResourceName = Identifier.New(grammar.GetOptionValue("pg.reduce.sr.ProductionNumberOutOfRange.name", "ProductionNumberOutOfRange"));
            ProductionNumberOutOfRangeResourceText = grammar.GetOptionValue("pg.reduce.sr.ProductionNumberOutOfRange.text", "Production number is out of range.");
        }

        public override void WriteFastSemanticAction(FastSemanticAction n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            WriteIdentifier(ResultValue, writer);
            writer.Write(" = ");
            WriteExpression(n.Value, writer);
            writer.WriteLine(";");
        }

        public override void WriteFullSemanticAction(FullSemanticAction n, IndentedWriter writer)
        {
            if (n == null)
                throw new ArgumentNullException(nameof(n));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            WriteStatement(n.Actions, writer);
        }

        public virtual void WriteProductionAction(GrammarProduction production, IndentedWriter writer)
        {
            if (production == null)
                throw new ArgumentNullException(nameof(production));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            if (production.Declarator.Action == null)
                writer.WriteLine("return null;");
            else
            {
                writer.OpenCurlyBlock();
                if (production.Owner.HasSemanticValue)
                {
                    WriteTypeExpression(production.Owner.Type, writer);
                    writer.Write(" ");
                    WriteIdentifier(ResultValue, writer);
                    writer.Write(" = default(");
                    WriteTypeExpression(production.Owner.Type, writer);
                    writer.WriteLine(");");
                }
                SourceContext context = production.Declarator.Context;
                if (context != null)
                {
                    int lineno = context.Begin.Line + 1;
                    string path = Path.GetFullPath(context.Document.FullName);
                    writer.WriteLine("// #line {0} \"{1}\"", lineno, path);
                }
                Production = production;
                WriteSemanticAction(production.Declarator.Action, writer);
                Production = null;
                if (context != null)
                    writer.WriteLine("// #line default");
                if (!production.Owner.HasSemanticValue)
                    writer.WriteLine("return null;");
                else
                {
                    writer.Write("return ");
                    WriteIdentifier(ResultValue, writer);
                    writer.WriteLine(";");
                }
                writer.CloseCurlyBlock();
            }
        }

        public virtual void WriteReduceMethod(GrammarProductionTable productions, IndentedWriter writer)
        {
            if (productions == null)
                throw new ArgumentNullException(nameof(productions));
            if (writer == null)
                throw new ArgumentNullException(nameof(writer));
            writer.Write(ReduceMethodAccessModifier);
            writer.Write(" ");
            if (UseStaticReduceMethod)
                writer.Write("static ");
            writer.Write("object ");
            WriteIdentifier(ReduceMethodName, writer);
            writer.Write("(int ");
            WriteIdentifier(ProductionNumberParameterName, writer);
            if (UseMoreParameters)
            {
                writer.Write(", ");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(ArrayListNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(ArrayListClassName, writer);
                writer.Write("<object> ");
                WriteIdentifier(SlotValues, writer);
                writer.Write(", ");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(ArrayListNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(ArrayListClassName, writer);
                writer.Write("<");
                if (UseFullyQualifiedNamespace)
                {
                    writer.Write("global::");
                    WriteDottedName(SourceContextNamespace, writer);
                    writer.Write(".");
                }
                WriteIdentifier(SourceContextClassName, writer);
                writer.Write("> ");
                WriteIdentifier(SlotContexts, writer);
            }
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            if (UseArgumentCheck)
            {
                writer.Write("if (");
                WriteIdentifier(ProductionNumberParameterName, writer);
                writer.WriteLine(" < 0)");
                writer.OpenBlock();
                writer.Write("throw new ");
                if (UseFullyQualifiedNamespace)
                    writer.Write("global::System.");
                writer.Write("ArgumentOutOfRangeException(");
                writer.Write("nameof(");
                WriteIdentifier(ProductionNumberParameterName, writer);
                writer.Write("), ");
                if (UseStringResources)
                {
                    if (UseFullyQualifiedNamespace)
                    {
                        writer.Write("global::");
                        WriteDottedName(StringResourcesNamespace, writer);
                        writer.Write(".");
                    }
                    WriteIdentifier(StringResourcesClassName, writer);
                    writer.Write(".");
                    WriteIdentifier(ProductionNumberMustBeNonNegativeResourceName, writer);
                }
                else
                {
                    WriteStringLiteral(ProductionNumberMustBeNonNegativeResourceText, writer);
                }
                writer.WriteLine(");");
                writer.CloseBlock();

                writer.Write("if (");
                WriteIdentifier(ProductionNumberParameterName, writer);
                writer.WriteLine(" >= {0})", productions.Count);
                writer.OpenBlock();
                writer.Write("throw new ");
                if (UseFullyQualifiedNamespace)
                    writer.Write("global::System.");
                writer.Write("ArgumentOutOfRangeException(");
                writer.Write("nameof(");
                WriteIdentifier(ProductionNumberParameterName, writer);
                writer.Write("), ");
                if (UseStringResources)
                {
                    if (UseFullyQualifiedNamespace)
                    {
                        writer.Write("global::");
                        WriteDottedName(StringResourcesNamespace, writer);
                        writer.Write(".");
                    }
                    WriteIdentifier(StringResourcesClassName, writer);
                    writer.Write(".");
                    WriteIdentifier(ProductionNumberOutOfRangeResourceName, writer);
                }
                else
                {
                    WriteStringLiteral(ProductionNumberOutOfRangeResourceText, writer);
                }
                writer.WriteLine(");");
                writer.CloseBlock();

                if (UseMoreParameters)
                {
                    writer.Write("if (");
                    WriteIdentifier(SlotValues, writer);
                    writer.WriteLine(" == null)");
                    writer.OpenBlock();
                    writer.Write("throw new ");
                    if (UseFullyQualifiedNamespace)
                        writer.Write("global::System.");
                    writer.Write("ArgumentNullException(");
                    writer.Write("nameof(");
                    WriteIdentifier(SlotValues, writer);
                    writer.WriteLine("));");
                    writer.CloseBlock();

                    writer.Write("if (");
                    WriteIdentifier(SlotContexts, writer);
                    writer.WriteLine(" == null)");
                    writer.OpenBlock();
                    writer.Write("throw new ");
                    if (UseFullyQualifiedNamespace)
                        writer.Write("global::System.");
                    writer.Write("ArgumentNullException(");
                    writer.Write("nameof(");
                    WriteIdentifier(SlotContexts, writer);
                    writer.WriteLine("));");
                    writer.CloseBlock();
                }
            }
            writer.Write("switch (");
            WriteIdentifier(ProductionNumberParameterName, writer);
            writer.WriteLine(")");
            writer.OpenCurlyBlock();
            foreach (GrammarProduction prod in productions)
            {
                writer.WriteLine("case {0}:", prod.Number);
                writer.OpenBlock();
                writer.WriteLine("// {0}", prod);
                WriteProductionAction(prod, writer);
                writer.CloseBlock();
            }
            writer.WriteLine("default:");
            writer.OpenBlock();
            writer.WriteLine("return null;");
            writer.CloseBlock();
            writer.CloseCurlyBlock();
            writer.CloseCurlyBlock();
        }
    }
}
